package com.cloud.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.cloud.gateway.bean.ReleaseInfo;
import com.cloud.gateway.bean.ResponseInfo;
import com.cloud.gateway.bean.SessionInfo;
import com.cloud.gateway.cache.SessionCache;
import com.cloud.gateway.constant.SysConstant;
import com.cloud.gateway.context.VersionHolder;
import com.cloud.gateway.core.ConfigLoader;
import com.cloud.gateway.limiter.LimiterManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;

@Component
public class ContextFilter implements GlobalFilter, Ordered {

    @Autowired
    private ConfigLoader configLoader;

    @Autowired
    private VersionHolder versionHolder;

    @Autowired
    private LimiterManager limiterManager;

    @Autowired
    private SessionCache sessionCache;

    @Override
    public int getOrder() {
        return -10;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        versionHolder.remove();
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //跨域请求初次调用直接通过
        if (request.getMethodValue().equalsIgnoreCase(RequestMethod.OPTIONS.toString())) {
            return response.setComplete();
        }

        String requestURI = request.getURI().getPath();
        if (!isExcludePath(requestURI)) {
            //静态资源
            if (isStaticResource(requestURI)) {
                String version = getVersionInfo(request);
                if (version != null) {
                    versionHolder.set(version);
                    return chain.filter(exchange);
                }
            }
            //获取令牌
            String token = getToken(request);
            if (token == null) {
                return responseCode(response, ResponseInfo.UNAUTHORIZED);
            }
            //检查令牌
            SessionInfo sessionInfo = sessionCache.check(token);
            if (sessionInfo == null) {
                return responseCode(response, ResponseInfo.UNAUTHORIZED);
            }
            //检查授权资源
            if (!sessionInfo.getAuthPaths().contains(requestURI)) {
                return responseCode(response, ResponseInfo.FORBIDDEN);
            }
            //限流控制
            if (!limiterManager.isAllow(requestURI, sessionInfo.getUserId())) {
                return responseCode(response, ResponseInfo.TOO_MANY_REQUESTS);
            }

            //获取版本设置到上下文
            String version = getReleaseVersion(sessionInfo);
            versionHolder.set(version);

            //设置请求头
            ServerHttpRequest.Builder requestBuilder = request.mutate();
            requestBuilder.header(SysConstant.TOKEN, token);
            requestBuilder.header(SysConstant.VERSION_INFO, version);
            requestBuilder.header(SysConstant.SESSION_INFO, JSON.toJSONString(sessionInfo));
            exchange = exchange.mutate().request(requestBuilder.build()).build();
        }
        return chain.filter(exchange);
    }

    /**
     * 输出结果
     * @param response
     * @param code
     */
    private Mono<Void> responseCode(ServerHttpResponse response, int code) {
        ResponseInfo responseInfo = new ResponseInfo(code);
        if (code == ResponseInfo.UNAUTHORIZED) {
            responseInfo.setMessage("会话过期，请重新登录");
        } else if (code == ResponseInfo.FORBIDDEN) {
            responseInfo.setMessage("权限不足，禁止访问");
        } else if (code == ResponseInfo.TOO_MANY_REQUESTS) {
            responseInfo.setMessage("请求太多，请稍后再试");
        }
        byte[] bytes = JSON.toJSONString(responseInfo).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Flux.just(buffer));
    }

    /**
     * 是否静态资源
     * @param requestURI
     * @return boolean
     */
    private boolean isStaticResource(String requestURI) {
        if (requestURI.contains(".")) {
            return true;
        }
        return false;
    }

    /**
     * 是否排除校验路径
     * @param requestURI
     * @return boolean
     */
    private boolean isExcludePath(String requestURI) {
        Set<String> excludePaths = configLoader.getExcludePaths();
        for (String path : excludePaths) {
            if (requestURI.startsWith(path)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取发布版本
     * @param sessionInfo
     * @return String
     */
    private String getReleaseVersion(SessionInfo sessionInfo) {
        List<ReleaseInfo> list = configLoader.getReleaseInfoList();
        for (ReleaseInfo releaseInfo : list) {
            String userId = releaseInfo.getUserId();
            String userArea = releaseInfo.getUserArea();
            if (userId.equals("*") || userArea.equals("*")) {
                return releaseInfo.getVersion();
            }
            if (userId.contains(sessionInfo.getUserId()) || userArea.contains(sessionInfo.getUserArea())) {
                return releaseInfo.getVersion();
            }
        }
        return null;
    }

    /**
     * 获取版本信息
     * @param request
     * @return String
     */
    private String getVersionInfo(ServerHttpRequest request) {
        HttpCookie cookie = request.getCookies().getFirst(SysConstant.VERSION_INFO);
        if (cookie != null) {
            return cookie.getValue();
        }
        return null;
    }

    /**
     * 获取令牌
     * @param request
     * @return String
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getQueryParams().getFirst(SysConstant.TOKEN);
        token = token == null ? request.getHeaders().getFirst(SysConstant.TOKEN) : token;
        if (token == null) {
            HttpCookie cookie = request.getCookies().getFirst(SysConstant.TOKEN);
            if (cookie != null) {
                token = cookie.getValue();
            }
        }
        return token;
    }

}
